/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.ainlolcat.aincalc2;
import org.ainlolcat.ainscience.tools;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 *
 * @author Администратор
 */
public class AinCalc {
    //region define state const
    // expects:          (       digit   symbol    dot     comma     *       )
    static byte[] r_state =    { 1,        1,      1,      0,      0,      0,      0 };
    static byte[] l_state =    { 0,        0,      0,      0,      1,      1,      1 };
    static byte[] d_state =    { 0,        1,      0,      1,      1,      1,      1 };
    static byte[] c_state =    { 1,        1,      1,      0,      0,      0,      0 };
    static byte[] a_state =    { 1,        1,      1,      0,      0,      0,      0 };
    static byte[] dot_state =  { 0,        1,      0,      0,      0,      0,      0 };
    static byte[] comma_state ={ 1,        1,      1,      0,      0,      0,      0 };
    static char L_BRAKE = '(';
    static char R_BRAKE = ')';
    static char CHAR_E = 'E';
    static char CHAR_DOT = '.';
    static char CHAR_COMMA = ',';
    static char CHAR_PLUS = '+';
    static char CHAR_SUB = '/';
    static char CHAR_MINUS = '-';
    static char CHAR_MULT = '*';
    static String STR_NEG_ONE = "-1";
    static Pattern letter = Pattern.compile("[a-zA-Z_]");

    public Cleks vchold;

    public AinCalc(Cleks in){
        vchold = in;
    }

    private boolean ALLOW_STRING_AS_ATTRIBUTE  = true;

    private List<Leksemma> parseLeksems(String com) throws UnexpectedSymbolExeption {
        ArrayList<Leksemma> leksems = new ArrayList();
        int right = 0; int left = 0; // счетчики скобок
        int s_pos = -1; // позиция начала команды или числа
        boolean isdot = false; //показывает, что в текущем числе есть точка
        boolean stringSequence = false;
        byte[] expect = r_state; // разрешенные состояния, уникальные для каждого

        for (int i = 0; i < com.length(); i++)
        {
            char current = com.charAt(i);
            //region bracket
            // (
            if (com.charAt(i) == L_BRAKE) {
                if (i != com.length() - 1 && expect[0] == 1) {
                    leksems.add(new Leksemma(L_BRAKE,Leksemma.TYPE_LBRACKET));
                    right++;
                    expect = r_state;
                    stringSequence = false;
                } else {
                    throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                }
            }
            // )
            if (com.charAt(i) == R_BRAKE)
            {
                if (expect[6] == 1) {
                    leksems.add(new Leksemma(R_BRAKE,Leksemma.TYPE_RBRACKET));
                    left++;
                    expect = l_state;
                    stringSequence = false;
                } else {
                    throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                }
            }
            //region Digit
            if (Character.isDigit(com.charAt(i)) && !stringSequence)
            {
                //if digit allowed
                if (expect[1] != 1)
                    throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                if (s_pos == -1) {
                    s_pos = i;
                }
                expect = d_state;
                stringSequence = false;
                //check next symbol
                if (i != com.length() - 1){
                    //if it's not digit
                    char next = com.charAt(i+1);
                    if (!(Character.isDigit(next)) && next != CHAR_E){
                        //but it still can be part of digit - dot. if not - let's end digit read there
                        if (com.charAt(i+1) != CHAR_DOT){
                            if (isdot){
                                leksems.add(new Leksemma(tools.parseDouble(com.substring(s_pos, i+1)),Leksemma.TYPE_DOUBLE));
                            }else{
                                leksems.add(new Leksemma(tools.parseInteger(com.substring(s_pos, i+1)),Leksemma.TYPE_INTEGER));
                            }
                            isdot = false;
                            s_pos = -1;
                        }
                    }
                }else {
                    if (isdot){
                        leksems.add(new Leksemma(tools.parseDouble(com.substring(s_pos, i + 1)),Leksemma.TYPE_DOUBLE));
                    }else{
                        leksems.add(new Leksemma(tools.parseInteger(com.substring(s_pos, i + 1)),Leksemma.TYPE_INTEGER));
                    }
                }
            }
            //region mathematical character
            if (com.charAt(i) == CHAR_MINUS){
                stringSequence = false;
                //check if last symbol
                if (i != com.length() - 1)
                {
                    //check if it 2*(-1) or 2-1
                    if (expect[5] == 1) {
                        leksems.add(new Leksemma(com.charAt(i),Leksemma.TYPE_OPERAND));
                        expect = a_state;
                    } else {
                        if ((expect[1] == 1)) {
                            leksems.add(new Leksemma(STR_NEG_ONE,Leksemma.TYPE_INTEGER));
                            leksems.add(new Leksemma(CHAR_MULT ,Leksemma.TYPE_OPERAND));
                            expect = a_state;
                        } else {
                            throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                        }
                    }
                } else {
                    throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                }
            }

            if ((com.charAt(i) == CHAR_PLUS) || (com.charAt(i) == CHAR_SUB) || (com.charAt(i) == CHAR_MULT)){
                stringSequence = false;
                if (i != com.length() - 1){
                    if (expect[5] == 1) {
                        leksems.add(new Leksemma(com.charAt(i),Leksemma.TYPE_OPERAND));
                        expect = a_state;
                    }
                    else {
                        throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                    }
                }
                else {
                    throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                }
            }
            //region dot
            if (com.charAt(i) == CHAR_DOT) {
                stringSequence = false;
                if (i != com.length() - 1){
                    if ((expect[3] == 1)&&(!isdot)) {
                        expect = dot_state;
                        isdot = true;
                    }
                    else {
                        throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                    }
                }
                else {
                    throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                }
            }
            //region comma
            if (com.charAt(i) == CHAR_COMMA){
                stringSequence = false;
                if (i != com.length() - 1){
                    if (expect[4] == 1) {
                        leksems.add(new Leksemma(com.charAt(i),Leksemma.TYPE_COMMA));
                        expect = comma_state;
                    }
                    else {
                        throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                    }
                }
                else {
                    throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                }
            }
            //region letter
            if (letter.matcher(String.valueOf(current)).matches() || (stringSequence && Character.isDigit(current)))
            {
                if (expect[1] == 1 && (current == CHAR_E)){
                    isdot = true;
                    continue;
                }
                stringSequence = true;
                if (expect[2] != 1)
                    throw new UnexpectedSymbolExeption(i,com, com.charAt(i));
                //if first letter occurrence remember position
                if (s_pos == -1) s_pos = i; // если до этого запоминаем начало команды

                String ufo = com.substring(s_pos, i+1 );
                // check if EOL
                if (i != com.length() - 1)
                {
                    char next = com.charAt(i + 1);
                    //check if next symbol is letter too
                    if (!(letter.matcher(String.valueOf(next)).matches() || (stringSequence && Character.isDigit(next)))) // следующий символ не буква.
                    {
                        //check if command
                        if (vchold.isCommand(ufo))
                        {
                            leksems.add(new Leksemma(ufo,Leksemma.TYPE_COMMAND));
                            expect = c_state;
                        }
                        else
                        {
                            //check if constant
                            if (vchold.isConstant(ufo))
                            {
                                Integer type = vchold.getConstType(ufo);
                                if (type.equals(Cleks.TYPE_INTEGER)){
                                    leksems.add(new Leksemma(vchold.getConstValue(ufo),Leksemma.TYPE_INTEGER));
                                } else if (type.equals(Cleks.TYPE_DOUBLE)){
                                    leksems.add(new Leksemma(vchold.getConstValue(ufo),Leksemma.TYPE_DOUBLE));
                                } if (type.equals(Cleks.TYPE_STRING)){
                                    leksems.add(new Leksemma(vchold.getConstValue(ufo),Leksemma.TYPE_STRING));
                                }
                                expect = d_state;
                            }
                            else
                            {
                                //check if variable
                                if (vchold.isVariable(ufo)){
                                    leksems.add(new Leksemma(ufo,Leksemma.TYPE_VARIABLE));
                                    expect = d_state;
                                }
                                else{
                                    //check if out command
                                    if (vchold.isOutCom(com.substring(s_pos, i+1))){
                                        leksems.add(new Leksemma(ufo,Leksemma.TYPE_OUT_COMMAND));
                                        expect = c_state;
                                    }
                                    //so it's must be string attribute of some operand
                                    else{
                                        leksems.add(new Leksemma(ufo,Leksemma.TYPE_STRING));
                                        expect = d_state;
                                    }
                                }
                            }
                        }
                        s_pos = -1;
                    }
                }
                //EOL:
                else
                {
                    if (vchold.isConstant(ufo))
                    {
                        Integer type = vchold.getConstType(ufo);
                        if (type.equals(Cleks.TYPE_INTEGER)){
                            leksems.add(new Leksemma(vchold.getConstValue(ufo),Leksemma.TYPE_INTEGER));
                        } else if (type.equals(Cleks.TYPE_DOUBLE)){
                            leksems.add(new Leksemma(vchold.getConstValue(ufo),Leksemma.TYPE_DOUBLE));
                        } if (type.equals(Cleks.TYPE_STRING)){
                            leksems.add(new Leksemma(vchold.getConstValue(ufo),Leksemma.TYPE_STRING));
                        }
                    }
                    else
                    {
                        if (vchold.isVariable(ufo)){
                            leksems.add(new Leksemma(ufo,Leksemma.TYPE_VARIABLE));
                        }
                        else{
                            leksems.add(new Leksemma(ufo,Leksemma.TYPE_STRING));
                        }
                    }
                }
            }
        }
        if (right > left) {
            throw new UnexpectedSymbolExeption(-1,com, R_BRAKE);
        }
        if (right < left) {
            throw new UnexpectedSymbolExeption(-1,com, L_BRAKE);
        }
        //for (int i = 0; i < leksems.Count; i++) Console.WriteLine(leksems[i].ToString());
        return leksems;
    }

    // делает элементарную проверку - проверяет явное нарушение следования символов или неверное написание команд, но не синтаксис
    // бъет на лексемы сообщение и запихивает их в массив.
    public Leksemma perform(String com) throws UnexpectedSymbolExeption, WrongParametersCount, UnknownCommandException {
        LeksemmasHandler doit = new LeksemmasHandler(vchold);
        return doit.start(parseLeksems(com));
    }
}
